package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.*;
import com.tianji.promotion.constants.PromotionConstants;
import com.tianji.promotion.domain.dto.CouponFormDTO;
import com.tianji.promotion.domain.dto.CouponIssueFormDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponPageVO;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.enmus.CouponStatus;
import com.tianji.promotion.enmus.ObtainType;
import com.tianji.promotion.enmus.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.ICouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * <p>
 * 优惠券的规则信息 服务实现类
 * </p>
 *
 * @author 晚风
 * @since 2023-09-27
 */
@Service
@RequiredArgsConstructor
public class CouponServiceImpl extends ServiceImpl<CouponMapper, Coupon> implements ICouponService {

    private final ICouponScopeService couponScopeService; // 优惠券的限定范围业务类
    private final IExchangeCodeService exchangeCodeService; // 兑换码业务类
    private final IUserCouponService userCouponService;
    private final StringRedisTemplate redisTemplate;

    @Override
    @Transactional
    public void saveCoupon(CouponFormDTO dto) {
        // 1. dto转po  保存优惠券 coupon表
        Coupon coupon = BeanUtils.copyBean(dto, Coupon.class);
        this.save(coupon);
        // 2. 判断dto.specific 如果为false 直接return；
        if (!dto.getSpecific()){
            return; // 说明没有限定优惠券的类型
        }
        // 3. 如果dto.specific 为true 需要校验dto.scopes
        if (CollUtils.isEmpty(dto.getScopes())){
            throw new BadRequestException("分类id不能为空");
        }
        // 4, 保存优惠券的限定范围
        List<Long> scopes = dto.getScopes();
        List<CouponScope> scList = scopes.stream()
                .map(aLong -> new CouponScope()
                        .setCouponId(coupon.getId()).setType(1).setBizId(aLong)).collect(Collectors.toList());
        couponScopeService.saveBatch(scList);
    }

    @Override
    public PageDTO<CouponPageVO> queryCouponPage(CouponQuery query) {
        // 1.查询数据库  :coupon
        Page<Coupon> page = this.lambdaQuery()
                .eq(query.getType() != null, Coupon::getDiscountType, query.getType())
                .eq(query.getStatus() != null, Coupon::getStatus, query.getStatus())
                .like(StringUtils.isNotBlank(query.getName()), Coupon::getName, query.getName())
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<Coupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)){
            return PageDTO.empty(page);
        }
        // 2.封装vo返回
        List<CouponPageVO> voList = BeanUtils.copyList(records, CouponPageVO.class);
        return PageDTO.of(page,voList);
    }

    @Override
    public void issueCoupon(Long id, CouponIssueFormDTO dto) {
        // 1. 校验
        if (id == null || !id.equals(dto.getId())){
            throw new BadRequestException("参数非法");
        }
        // 2. 校验优惠券id是否存在
        Coupon coupon = getById(id);
        if (coupon == null){
            throw new BadRequestException("优惠券不存在");
        }
        // 3. 校验优惠券状态  只有未发放和暂停状态才能发放
        if (coupon.getStatus() != CouponStatus.DRAFT && coupon.getStatus() != CouponStatus.PAUSE){
            throw new BizIllegalException("只有暂停和带发放的优惠券才能发放");
        }
        LocalDateTime now = LocalDateTime.now();
        // 该变量代表优惠券 是否立即发放
        boolean isBeginIssue = dto.getIssueBeginTime() == null || !dto.getIssueBeginTime().isAfter(now);
        // 4. 修改优惠券的领取开始和结束时间  使用开始和结束时间 天数  状态
        Coupon tmp = BeanUtils.copyBean(dto, Coupon.class);
        if (isBeginIssue){
            tmp.setIssueBeginTime(now);
            tmp.setStatus(CouponStatus.ISSUING);
        }else {
            tmp.setStatus(CouponStatus.UN_ISSUE);
        }
        // 调用修改方法
        this.updateById(tmp);
        //5. 如果优惠券是立刻发放 将优惠券信息（优惠券id，领券开始时间结束时间，发行总数量，限领数量） 采用HASH 存入redis
        if (isBeginIssue){
            Map<String, String> map = new HashMap<>();
            map.put("issueBeginTime",String.valueOf(DateUtils.toEpochMilli(now)));
            map.put("issueEndTime",String.valueOf(DateUtils.toEpochMilli(dto.getIssueEndTime())));
            map.put("totalNum",String.valueOf(coupon.getTotalNum()));
            map.put("userLimit",String.valueOf(coupon.getUserLimit()));
            String key = PromotionConstants.COUPON_CACHE_KEY_PREFIX + id; // prs:coupon:优惠券id
            redisTemplate.opsForHash().putAll(key,map);
        }

        // 6. 如果优惠券的发放方式是 指定发放  且  优惠券的状态需要是带发放则需要生成兑换码
        if (coupon.getObtainWay() == ObtainType.ISSUE && coupon.getStatus() == CouponStatus.DRAFT){
            // 需要生成兑换码
            coupon.setIssueEndTime(tmp.getIssueEndTime()); // 兑换码兑换截止时间  就是优惠券领取的截止时间 该时间是从前端传的  封装在tmp
            exchangeCodeService.asyncGenerateExchangeCode(coupon); // 异步生成兑换码
        }

    }

    // 查询正在发放中的优惠券
    @Override
    public List<CouponVO> queryIssuingCoupons() {
        // 1.查询数据库 coupon 条件 状态发放中 手动领取
        List<Coupon> couponList = this.lambdaQuery()
                .eq(Coupon::getStatus, CouponStatus.ISSUING)
                .eq(Coupon::getObtainWay, ObtainType.PUBLIC)
                .list();
        if (CollUtils.isEmpty(couponList)){
            return CollUtils.emptyList();
        }
        // 正在发放中的优惠券id集合
        Set<Long> couponIds = couponList.stream().map(Coupon::getId).collect(Collectors.toSet());
        // 2. 查询用户卷表 user_coupon 条件当前用户  发放中的优惠券 id
        // 当前用户  针对正在发放中的优惠券领取记录
        List<UserCoupon> list = userCouponService.lambdaQuery()
                .eq(UserCoupon::getUserId, UserContext.getUser())
                .in(UserCoupon::getCouponId, couponIds)
                .list();

        // 2.1 统计当前用户  针对每一个卷 的已领数量
        Map<Long, Long> issueMap = list.stream().collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        // 2.2 统计当前用户  针对每一个卷 的已领且未使用的数量
        Map<Long, Long> unuseMap = list.stream()
                .filter(c -> c.getStatus() == UserCouponStatus.UNUSED)
                .collect(Collectors.groupingBy(UserCoupon::getCouponId, Collectors.counting()));
        // 3.po封装vo
        List<CouponVO> voList = new ArrayList<>();
        for (Coupon c : couponList) {
            CouponVO vo = BeanUtils.copyBean(c, CouponVO.class);
            // 优惠券还有剩余 （issue_num < total_num） 且 （统计用户表user_coupon 取出当前用户已领数量  < user_limit）
            Long orDefault = issueMap.getOrDefault(c.getId(), 0L);
            boolean availabe = c.getIssueNum() < c.getTotalNum() && orDefault <c.getUserLimit();
            vo.setAvailable(availabe); // 是否可以领取
            // 统计用户表user_coupon 取出当前已经且未使用的卷数量
            boolean received = unuseMap.getOrDefault(c.getId(),0L) > 0;
            vo.setReceived(received); // 是否可以使用
            voList.add(vo);
        }
        return voList;
    }
}
